{% extends "codeflow/reviewer/base.html" %}
{% load i18n %}
{% block page_head %}
<script src="/media/scripts/MochiKit.js"></script>
<script src="/media/scripts/DragAndDrop.js"></script>
<script src="/media/scripts/prettify.js"></script>
<link href="/media/prettify.css" type="text/css" rel="stylesheet" />
<style type="text/css">
  table.diff { font-family: monospace;  border:medium; width: 100%; }
  .diff_header { background-color:#e0e0e0; }
  th.diff_header { width: 49%; }
  td.diff_header { text-align:right; cursor: pointer; }
  td.diff_header.has_comments { color: white; background-color: blue; }
  .diff_next { background-color:#c0c0c0; }
  .diff_add { background-color:#aaffaa; }
  .diff_chg { background-color:#ffff77; }
  .diff_sub { background-color:#ffaaaa; }
  .comment_author { text-align: right; }
</style>

<script type="text/javascript">


  var DiffTable = {
      COMPLETE: 'COMPLETE',
      CONTEXT: 'CONTEXT',
      HIGHLIGHT: true,
      NO_HIGHLIGHT: false,

      _tableIdRegExp: /difflib_chg_to([0-9]+)__top/,
      _diffs: {COMPLETE:{}, CONTEXT: {}}, // Cache

/***
TODO: Translate to english

:mochidef:`DiffTable.load(path, changesetScmId, kind, container,
                          syntaxHighlight=DiffTable.NO_HIGHLIGHT)`

Loads a diff for the file identified by `path`, comparing its contents before
and after the changeset identified by `changesetScmId`.

El parámetro `kind` puede tomar dos valores: `DiffTable.COMPLETE` o
`DiffTable.CONTEXT`, según se requiera que el diff incluya el contenido
completo del archivo o sólo las partes cercanas a las líneas modificadas.

El contenido del diff será colocado dentro del elemento DOM `container`,
sobreescribiendo cualquier otro hijo que este contenedor pudiera tener.

Si `syntaxHighlight` == DiffTable.HIGHLIGHT, se colorea la sintaxis de la
tabla. *WARNING*: Esto puede relentizar el navegador. De hecho, una tabla
con resaltado de sintaxis tarda alrededor de 6 veces mas en dibujarse. Se
requiere http://code.google.com/p/google-code-prettify/.

Internamente, se llama asíncronamente a /browse/file/htmldiff/ con los
argumentos correspondientes para obtener una tabla html generada por
difflib.HtmlDiff.

***/


      load: function(path, changesetScmId, kind, container, syntaxHighlight) {
          var cache = DiffTable._cacheForKind(kind);
          if (!(path in cache)) {
              DiffTable._requestDiff(path, changesetScmId, kind, container,
                                     syntaxHighlight);
          } else {
              DiffTable._render(path, changesetScmId, kind, container,
                                syntaxHighlight);
          }
      },

      isDiffTable: function(element) {
          return element.tagName.toUpperCase() == "TABLE" &&
                 element.id.match(DiffTable._tableIdRegExp);
      },

      cellForLineNumber: function(tableOrContainer, lineNumber,
                                  whichRevision) {
          var table = null;
          if (DiffTable.isDiffTable(tableOrContainer)) {
              table = tableOrContainer;
          } else {
              table = DiffTable._getFirstChildTable(tableOrContainer);
              if (!DiffTable.isDiffTable(table)) {
                  logError("DiffTable.cellForLineNumber: element is not a " +
                           "valid difftable or container");
                  return null;  //FIXME: isn't better to throw an exception?
              }
          }

          var serialNumber = DiffTable._tableIdRegExp.exec(table.id)[1];
          var cellId = (whichRevision == 'o' ? 'from' : 'to') +
                       serialNumber + '_' + lineNumber;
          return getElement(cellId);


      },

      _render: function(path, changesetScmId, kind, container, syntaxHighlight) {
          container.innerHTML = DiffTable._cacheForKind(kind)[path];
          var table = DiffTable._getFirstChildTable(container);
          if (syntaxHighlight) {
              DiffTable._syntaxHighlight(table);
          }

          signal(DiffTable, 'tableRendered', path, changesetScmId, kind,
                 container, table);
      },

      _syntaxHighlight: function(table) {
          forEach(table.tBodies, function(tbody) {
              forEach(tbody.rows, function(row) {
                  var cols = row.cells;
                  if (cols.length == 6) {
                      if (scrapeText(cols[2]).replace(' ', '').replace('\n', '') != '')
                          cols[2].innerHTML = prettyPrintOne(cols[2].innerHTML);
                      if (scrapeText(cols[5]).replace(' ', '').replace('\n', '') != '')
                          cols[5].innerHTML = prettyPrintOne(cols[5].innerHTML);
                  }
              });
          });
      },

      _requestDiff: function(path, changesetScmId, kind, container, syntaxHighlight) {
          var args = {'path': path,
                      'changeset_scm_id': changesetScmId};
          if (kind == DiffTable.COMPLETE) {
              args['complete'] = DiffTable.COMPLETE;
              // For context diffs the complete arguments must not be part
              // of the request.
          }
          var d = doSimpleXMLHttpRequest('/browse/file/htmldiff/', args);
          d.addCallback(DiffTable._receiveDiff, path, changesetScmId, kind,
                        container, syntaxHighlight);
          d.addErrback(logError); //TODO: Better error handling
      },

      _receiveDiff: function(path, changesetScmId, kind, container,
                             syntaxHighlight, xhr) {
          var cache = DiffTable._cacheForKind(kind);
          cache[path] = xhr.responseText;
          DiffTable._render(path, changesetScmId, kind, container, syntaxHighlight);
      },

      _cacheForKind: function(kind) {
          var cache = DiffTable._diffs[kind];
          if (isUndefinedOrNull(cache)) {
              logWarning("DiffTable: kind " + kind + " not recognized");
              kind = DiffTable.CONTEXT;
              logWarning("DiffTable: using kind = " + kind);
              cache = DiffTable._diffs[kind];
          }
          return cache;
      },

      _getFirstChildTable: function(container) {
          return filter(function(el){return el.tagName == "TABLE"},
                        container.childNodes)[0];
      }

  };

  var existingComments = []
  {% if cset_review %}
  {% for comment in cset_review.inline_comments.all %}
  existingComments.push({'path': '{{ comment.file_path|addslashes }}',
                         'whichRevision': '{{ comment.which_revision|addslashes }}',
                         'lineno': {{ comment.line_number }},
                         'text': '{{ comment.text|addslashes }}',
                         'author': '{{ comment.author.username|addslashes }}'});
  {% endfor %}
  addLoadEvent(function(){map(showExistingComment, existingComments)});
  {% endif %}


  var addedComments = [];
  var diffTables = {}

  function addComment(path, whichRevision, lineno, text) {
      c = {};
      c['path'] = path;
      c['whichRevision'] = whichRevision;
      c['lineno'] = lineno;
      c['text'] = text;
      c['author'] = 'Me';
      addedComments.push(c);
      showAddedComment(c);
      highlightLine(c);
  }

  function showComment(type, c) {
      $(type + '_comments').appendChild(TR(null,
                                           TD(null, c.path),
                                           TD(null,
                                              c.whichRevision == 'o' ? 'old':
                                                                       'new'),
                                           TD(null, c.lineno),
                                           TD(null, c.text),
                                           TD(null, c.author)));
  }

  function showAddedComment(c) {
     showComment('added', c);
  }

  function showExistingComment(c) {
      showComment('existing', c);
  }


  function highlightLine(c) {
      var table = diffTables[c.path];
      var cell = DiffTable.cellForLineNumber(table, c.lineno, c.whichRevision);
      addElementClass(cell, 'has_comments');
  }

  function getComments(path, whichRevision, lineno) {
      var match = function(c) {
          return c.path == path &&
                 c.whichRevision == whichRevision &&
                 c.lineno == lineno;
      };
      return filter(match, concat(existingComments, addedComments));
  }


  function onTableRendered(path, changesetScmId, kind, container, table) {
      diffTables[path] = table;
      installReviewSupport(path);
      // XXX: Maybe this should be in another function?:
      forEach(filter(function(c){return c.path == path},
                     concat(existingComments, addedComments)),
              highlightLine);
  }
  connect(DiffTable, 'tableRendered', onTableRendered);

  function createWindow(caption) {
      var closeButton = A({href:'javascript:return true'}, '(close)');
      var title = H3(null, caption, ' ', closeButton);
      var win = DIV({style: {position: 'absolute', width: '40em'}},
                    title);
      var dw = new Draggable(win, {handle: title});
      connect(closeButton, 'onclick', partial(removeElement, win));
      return win;
  }

  function commentWindow(path, whichRevision, lineno, evt) {
      var win = createWindow('Comments');
      var header =  createDOM('H4', {style: {textAlign: 'left'}},
                              "Comments for " +
                              (whichRevision == 'o' ? 'old ': 'new ') +
                              path +
                              " (line " + lineno+ ")");
      var textbox = TEXTAREA({rows: '4', style: {width: '99%'}});
      var cancel = INPUT({type: 'button', value: 'Cancel'});
      connect(cancel, 'onclick', partial(removeElement, win));
      var ok = INPUT({type: 'button', value: 'OK'});
      connect(ok, 'onclick', function() {
          addComment(path, whichRevision, lineno, textbox.value);
          removeElement(win);
      });

      var existingComments = getComments(path, whichRevision, lineno);
      var renderComment = function(c) {
          return [P({'class': 'comment'}, c.text),
                  P({'class': 'comment_author'}, c.author),HR()];
      }
      var content = DIV({'class': 'block'},
                        header,
                        HR(),
                        map(renderComment, existingComments),
                        textbox,
                        HR(),
                        ok, cancel);

      win.appendChild(content);
      document.body.appendChild(win);
      var pos = elementPosition(evt.src());
      var dim = elementDimensions(evt.src());
      pos.y += dim.h;
      setElementPosition(win, pos);
      textbox.focus();
  }

  function installReviewSupport(path) {
      var table = diffTables[path];
      forEach(table.tBodies, function(tbody) {
          forEach(tbody.rows, function(row) {
              var cols = row.cells;
              if (cols.length == 6) {

                  if (cols[1].id && cols[1].id.match(/^from/)) {
                      var lineno = cols[1].id.split("_")[1];
                      connect(cols[1], 'onclick',
                              partial(commentWindow, path, 'o', lineno));

                  }
                  if (cols[4].id && cols[4].id.match(/^to/)) {
                      var lineno = cols[4].id.split("_")[1];
                      connect(cols[4], 'onclick',
                              partial(commentWindow, path, 'n', lineno));
                  }
              }
          });
      });
  }

  function hideDiff(path) {
      diffTables[path] = undefined;
      getElement(path + '_changes').innerHTML = "";
  }

  //API used in the HTML

  function diffFile(path, changesetScmId, complete) {

      var kind = isUndefinedOrNull(complete) ? DiffTable.CONTEXT :
                                               DiffTable.COMPLETE;
      var container = getElement(path + '_changes');
      DiffTable.load(path, changesetScmId, kind, container, DiffTable.HIGHLIGHT);
  }

  function hideFile(path) {
      hideDiff(path);
  }

  function diffAll(changesetScmId, complete) {
      {% for changed_path in cset.changed_paths %}
      diffFile('{{ changed_path }}', changesetScmId, complete);
      {% endfor %}
  }
  function hideAll() {
      {% for changed_path in cset.changed_paths %}
      hideFile('{{ changed_path }}');
      {% endfor %}
  }

  function send() {
      var form = getElement('review_form');
      var serializeAsInputs = function(obj, prefix) {
          var inputs = [];
          for(prop in obj) {
              inputs.push(INPUT({type: 'hidden',
                                 name: prefix + prop,
                                 value: obj[prop]}));
          }
          return inputs;
      };

      forEach(addedComments, function(c) {
          appendChildNodes(form,
                           serializeAsInputs(c, 'comment_'));
      });

      form.submit()
  }

  function exclusiveCheck(check1, check2) {
      if (check1.checked) {
          check2.checked = false;
      }
  }

</script>

{% endblock %}
{% block content %}
<h1>
    {% if review.passed %}
    <img src="/media/icons/review_passed.png">
    {% endif %}
    {% if review.failed %}
    <img src="/media/icons/review_failed.png">
    {% endif %}
Review {% if review %} {{ review.id }}: {% endif %}
     Changeset {{ cset.scm_id }}
 </h1>
{% if review %}
<div class="section">
  <h2> Review Info </h2>
  <ul>
    <li>Id: {{review.id }}</li>
    <li>Reviewer: {{ review.author }}</li>
    <li>Date: {{ review.date }}
    <li>Status:
    {% if review.passed %} Passed
    {% else %} {% if review.failed %} Failed
    {% else %} Pending
    {% endif %}
    {% endif %}
  </ul>
</div>
{% endif %}
<div class="section">
  <h2> Changeset Info </h2>
  <ul>
    <li>Id: {{cset.scm_id}}</li>
    <li>Author: {{cset.scm_user}}</li>
    <li>Date: {{cset.date}}</li>
    <li>Message: {{cset.message|escape}}</li>
  </ul>
</div>

<div class="section">
  <h2> Changes </h2>
  <a href="javascript:diffAll('{{ cset.scm_id }}')">Show All Changes</a>
  <a href="javascript:diffAll('{{ cset.scm_id }}', 'complete')">Show All Files</a>
  <a href="javascript:hideAll()">Hide All</a>
  <table id="changes_table">
    <thead>
      <tr>
        <th style="width: 30em;">File Name</th>
        <th style="width: 7em;"></th>
        <th style="width: 5em;"></th>
        <th></th>
      </tr>
    </thead>
    <tbody>
      {% for changed_path in cset.changed_paths %}
      <tr>
        <td>{{ changed_path }}</td>
        <td><a href="javascript:diffFile('{{ changed_path }}', '{{ cset.scm_id}}')">
            Show Changes</a></td>
        <td><a href="javascript:diffFile('{{ changed_path }}', '{{ cset.scm_id}}', 'complete')">
            Show File</a></td>
        <td><a href="javascript:hideFile('{{ changed_path }}')">Hide</a></td>
      </tr>
      <tr>
        <td colspan="4" id="{{ changed_path }}_changes">
        </td>
      </tr>
      {% endfor %}
    </tbody>
  </table>

</div>
<form name="review_form" id="review_form" method="post"
      {% if not review %} action="/review/create/changeset/" {% endif %}>
  <input type="hidden" name="changeset_scm_id" value="{{ cset.scm_id }}"/>
  <h3> General Comments </h3>
  <div class="block">
    {% if cset_review %}
    {% for comment in cset_review.comments.all %}
    <p class="comment">{{ comment.text|escape }}</p>
    <p class="comment_author"> {{ comment.author.username }} </p>
    <hr />
    {% endfor %}
    {% endif %}
    <textarea name="general_comment" id="general_comment_text"
              style="width:100%"></textarea>
  </div>
  <h3> Inline Comments </h3>
  <div class="block">
    <p>
    This is a summary of the comments made on the changes. To make more
    inline comments or view them into their context, click on the line
    number column on the diffs shown above.
    </p>
    <table width="100%">
      <thead>
        <tr>
          <th width="30%">File Name</th>
          <th width="10%">Which Rev</th>
          <th width="10%">Line</th>
          <th width="40%">Text</th>
          <th width="10%">Author</th>
        </tr>
      </thead>
      <tbody id="existing_comments">
      </tbody>
      <tbody id="added_comments">
      </tbody>
    </table>
  </div>
  <h3> Review Status </h3>
  <div class="block">
    <table width="100%">
      <tr>
        {% if review %}
        {% ifequal review.author user %}
        <td width="33%">
          <label for="review_passed_checkbox">Passed</label>
          <input type="checkbox" name="review_passed"
                 id="review_passed_checkbox"
                 onchange="exclusiveCheck(this, $('review_failed_checkbox'))"
                 {% if review.passed %} checked="checked" {% endif %}/>
        </td>
        <td width="33%">
          <label for="review_failed_checkbox">Failed</label>
          <input type="checkbox" name="review_failed"
                 id="review_failed_checkbox"
                 onchange="exclusiveCheck(this, $('review_passed_checkbox'))"
                 {% if review.failed %} checked="checked" {% endif %}/>
        </td>
        {% else %}
        <td width="50%">
          {% if review.passed %}
          <pre> <img src="/media/icons/review_passed.png"> Passed </pre>
          {% else %}
          {% if review.failed %}
          <pre> <img src="/media/icons/review_failed.png"> Failed </pre>
          {% else %}
          <pre> Pending </pre>
          {% endif %}
          {% endif %}
        </td>
        {% endifequal %}
        {% else %}
        <td width="33%">
          <label for="review_passed_checkbox">Passed</label>
          <input type="checkbox" name="review_passed"
                 id="review_passed_checkbox"
                 onchange="exclusiveCheck(this, $('review_failed_checkbox'))"/>
        </td>
        <td width="33%">
          <label for="review_failed_checkbox">Failed</label>
          <input type="checkbox" name="review_failed"
                 id="review_failed_checkbox"
                 onchange="exclusiveCheck(this, $('review_passed_checkbox'))"/>
        </td>
        {% endif %}
        <td>
          <input type="button" value="Send" onclick="send()"/>
        </td>
      </tr>
    </table>
  </div>
</form>
{% endblock %}
